﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;

namespace ChartEdit
{

    public class Renderer2
    {
        private PreRenderer pr;
        private Editor ed;

        private int width { get { return ed.Width; } }
        private int height { get { return ed.Height; } }

        public Bitmap img;
        private Graphics g;
        private Graphics gLQ;
        private GraphicsState[] gState = new GraphicsState[4];



        // Rendering configuration stuff
        public volatile bool Busy;
        public bool DrawGlobalEvents;
        public bool DrawTrackEvents;
        public bool DrawSyncEvents;
        public bool DrawSections;
        public bool DrawNotes;
        public RenderingMode RenderingMode
        {
            get { return RenderingMode.HighQuality; }
            set {}
        }

        private Pen[] beatPen;
        private Pen fbEdgePen;
        private Pen songPen;
        private Pen syncPen;
        private Pen sectionPen;
        private Pen tEventPen;
        private Pen gEventPen;

        private Font bpmFont;
        private Brush bpmFontColor;

        private Font debugFont;
        private Brush debugFontColor;

        private Font sectionFont;
        private Brush sectionFontColor;

        private Font tEventFont;
        private Brush tEventFontColor;

        private Font gEventFont;
        private Brush gEventFontColor;

        /// <summary>
        /// Brush to use for specials
        /// </summary>
        private List<SolidBrush> specialBrush;

        //Lag detection
        private System.Diagnostics.Stopwatch sW;
        private float[] executionTimes = new float[32];
        private int eIndex = 0;
        //fps
        private ulong count = 0;
        private float[] fps = new float[32];

        //Clock
        private System.Diagnostics.Stopwatch clock;
        private float clockSnapshot;

        public void Resize()
        {
            img = new Bitmap((Image)img, Math.Max(width, 10), Math.Max(height, 10));
            this.g = Graphics.FromImage(img);
        }

        public void Render(RenderingMode renderingMode)
        {
            Busy = true;
            //lock (ed)
            {
                clockSnapshot = ((float)clock.Elapsed.Ticks / System.TimeSpan.TicksPerSecond);
                fps[eIndex] = 1 / ((float)sW.Elapsed.Ticks / System.TimeSpan.TicksPerSecond);
                sW.Reset();
                sW.Start();

                // Start Drawing
                //int alpha = (int)Math.Min(255 * (60f/fps.Average()), 255);
                g.Clear(Color.FromArgb(255,0,0,0));

                drawSpecials();
                drawBeatLines();
                //Draw fretboard edges
                g.DrawLine(fbEdgePen, ed.FbX, 0f, ed.FbX, (float)height);
                g.DrawLine(fbEdgePen, ed.FbXR, 0f, ed.FbXR, (float)height);
                drawHitBar();
                drawNoteSustains();
                drawNotes();
                drawGhostNotes();
#if DEBUG
                drawBoundingBoxes();
#endif
                // Stop Drawing

                executionTimes[eIndex] = (float)sW.Elapsed.Ticks / TimeSpan.TicksPerSecond;
                pr.RendererLag = executionTimes.Average();
#if DEBUG
                count++;
                g.Restore(gState[(int)RenderingMode.LowQuality]);
                drawString("Lag: " + (pr.Lag * 1000f).ToString() + "ms", debugFont, debugFontColor, 2, 2);
                drawString("FPS: " + fps.Average().ToString(), debugFont, debugFontColor, 2, 22);
                drawString(count.ToString() + " iterations", debugFont, debugFontColor, 2, 42);
                drawString(ed.rSW.Elapsed.Milliseconds.ToString(), debugFont, debugFontColor, 2, 62);
                g.Restore(gState[(int)RenderingMode]);
#endif
                eIndex++;
                eIndex %= executionTimes.Length;
            }
            Busy = false;
        }

        private void drawHitBar()
        {

            float X;
            g.DrawLine(songPen, ed.FbX, pr.YHit, ed.FbXR, pr.YHit);
            g.DrawLine(songPen, ed.FbX, pr.DebugSnapY, ed.FbXR, pr.DebugSnapY);
            for (int i = 0; i < ed.currentTheme.Elements.Count(); i++)
            {
                ChartElement c = ed.currentTheme[i];
                X = ed.FbXB + c.Offset.X * ed.FbWidthB;
                if (c.Offset.X >= 0 & c.Offset.X <= 1)
                    g.DrawImage(c["hit"].ImageResized, X + c.BoundingBoxResized.X, pr.YHit + c.BoundingBoxResized.Y);
            }
        }

        private void drawBoundingBoxes()
        {
            string dS;
            string name;
            foreach (RendererNote rn in ed.CData.Notes)
            {
                g.FillRectangle(specialBrush[1], rn.SustainXYf.X+rn.BBox.X, rn.SustainXYf.Y+rn.BBox.Y, rn.BBox.Width, (rn.XYf.Y - rn.SustainXYf.Y) + rn.BBox.Height);
                dS = "Note " + ed.noteTrack.IndexOf(rn.Note).ToString() + "\n";
                name = ed.currentTheme[rn.Note.Fret].Name;
                name = name.Substring(0,1).ToUpper() + name.Substring(1);
                dS += name +"\n";
                dS += "Length: " + rn.Note.Length.ToString() +"\n";
                drawString(dS, debugFont, debugFontColor, rn.XYf.X + rn.BBox.Width/2, rn.XYf.Y);
                
            }
        }

        private void drawSpecials()
        {
            foreach (RendererSpecial s in pr.SpecialList)
            {
                g.FillPolygon(specialBrush[s.SIndex], s.ToArray());
            }
        }

        private void drawBeatLines()
        {
            float X1 = ed.FbX;
            float X2 = ed.FbXR;
            foreach (RendererBeatLine b in pr.BeatList)
            {
                g.DrawLine(beatPen[(byte)b.BeatType], X1, b.Y, X2, b.Y);
            }
        }

        private void drawString(string s, Font font, Brush brush, float x, float y)
        {
            g.DrawString(s, font, Brushes.Black, x+1, y+1);
            g.DrawString(s, font, Brushes.Black, x - 1, y + 1);
            g.DrawString(s, font, Brushes.Black, x - 1, y - 1);
            g.DrawString(s, font, Brushes.Black, x + 1, y - 1);
            g.DrawString(s, font, brush, x, y);
        }

        /// <summary>
        /// Draws the notes
        /// </summary>
        /// 

        private void drawNote(RendererNote n)
        {
            if (n.CEP.SpinRate!=0) //ROTATINS
            {
                double dir = ((double)clockSnapshot*n.CEP.SpinRate)%(Math.PI*2);
                float x = n.XYf.X;
                float y = n.XYf.Y;
                float r = (float)Math.Sqrt((n.BBox.Width * n.BBox.Width) + (n.BBox.Height * n.BBox.Height))/2;
                double d1 = (Math.PI-Math.Atan2((double)n.BBox.Height, (double)n.BBox.Width)) + dir;
                double d2 = Math.Atan2((double)n.BBox.Height, (double)n.BBox.Width) + dir;
                double d3 = Math.Atan2((double)n.BBox.Height, (double)n.BBox.Width) + Math.PI + dir;

                g.DrawImage(n.CEP.ImageResized, new PointF[]{
                new PointF((float)(x+r*Math.Cos(d1)),(float)(y-r*Math.Sin(d1))),
                new PointF((float)(x+r*Math.Cos(d2)),(float)(y-r*Math.Sin(d2))),
                new PointF((float)(x+r*Math.Cos(d3)),(float)(y-r*Math.Sin(d3)))});
                return;
            }
            g.DrawImage(n.CEP.ImageResized, n.XYf.X + n.BBox.X, n.XYf.Y + n.BBox.Y);
        }

        private void drawNotes()
        {
            if (ed.song.IsPlaying())
            {
                foreach (RendererNoteList nl in pr.NoteLists)
                {
                    foreach (RendererNote n in nl)
                    {
                        if (n.XYf.Y < pr.YHit)
                            drawNote(n);
                    }
                }
            }
            else
            {
                foreach (RendererNoteList nl in pr.NoteLists)
                {

                    foreach (RendererNote n in nl)
                    {
                        drawNote(n);
                    }
                }
            }
        }

        private void drawGhostNotes()
        {
            foreach (RendererNote n in pr.GhostNoteList)
            {
                if (n.Note.OffsetEnd > n.Note.Offset)
                    g.DrawLine(n.CE.SustainPen, n.XYf, n.SustainXYf);
            }
            foreach (RendererNote n in pr.GhostNoteList)
            {
                drawNote(n);
            }
        }

        private void drawNoteSustains()
        {
            if (ed.song.IsPlaying())
            {
                PointF xy=new PointF();
                foreach (RendererNoteList nl in pr.NoteLists)
                {
                    foreach (RendererNote n in nl)
                    {
                        if (n.Note.OffsetEnd <= n.Note.Offset)
                            continue;
                        if (n.SustainXYf.Y > pr.YHit)
                            continue;
                        xy.Y = Math.Min(pr.YHit, n.XYf.Y);
                        xy.X = n.XYf.X;
                        g.DrawLine(n.CE.SustainPen, xy, n.SustainXYf);
                    }
                }
            }
            else
            {
                foreach (RendererNoteList nl in pr.NoteLists)
                {
                    foreach (RendererNote n in nl)
                    {
                        if (n.Note.OffsetEnd > n.Note.Offset)
                            g.DrawLine(n.CE.SustainPen, n.XYf, n.SustainXYf);
                    }
                }
            }
        }

        private void Initialize()
        {

            beatPen = new Pen[6];
            beatPen[(byte)beatType.Measure] = new Pen(Brushes.White, 6);
            beatPen[(byte)beatType.Beat] = new Pen(Brushes.White, 3);
            beatPen[(byte)beatType.HalfBeat] = new Pen( Brushes.Gray, 1);
            beatPen[(byte)beatType.QuarterBeat] = null;
            beatPen[(byte)beatType.EighthBeat] = null;
            beatPen[(byte)beatType.SixteenthBeat] = null;

            songPen = new Pen(Color.FromArgb(192, 255, 0, 0), 8);
            syncPen = new Pen(Color.FromArgb(128, 0, 255, 0), 8);
            sectionPen = new Pen(Color.FromArgb(128, 255, 255, 0), 8);
            tEventPen = new Pen(Color.FromArgb(98, 25, 0, 255), 8);
            gEventPen = new Pen(Color.FromArgb(98, 180, 255, 255), 8);
            fbEdgePen = new Pen(Brushes.White, 8);

            specialBrush = new List<SolidBrush>();
            specialBrush.Add(new SolidBrush(Color.FromArgb(128, 255, 255, 0)));
            specialBrush.Add(new SolidBrush(Color.FromArgb(128, 255, 0, 0)));
            specialBrush.Add(new SolidBrush(Color.FromArgb(128, 0, 32, 255)));

            bpmFont = new Font("Arial", 10);
            sectionFont = new Font("Arial", 10);
            tEventFont = new Font("Arial", 10);
            gEventFont = new Font("Arial", 10);
            bpmFontColor = new SolidBrush(Color.FromArgb(255, 0, 255, 0));
            sectionFontColor = new SolidBrush(Color.FromArgb(255, 255, 255, 0));
            tEventFontColor = new SolidBrush(Color.FromArgb(255, 0, 32, 255));
            gEventFontColor = new SolidBrush(Color.FromArgb(255, 0, 128, 255));

            debugFont = new Font("Arial", 10);
            debugFontColor = new SolidBrush(Color.FromArgb(255,255,255,255));

        }

        public Renderer2(Editor ed, PreRenderer pr)
        {
            sW = new System.Diagnostics.Stopwatch();
            sW.Start();
            clock = new System.Diagnostics.Stopwatch();
            clock.Start();
            Initialize();

            this.ed = ed;
            this.pr = pr;

            this.img = new Bitmap(width, height);
            //this.img2 = new Bitmap(width, height);
            this.g = Graphics.FromImage(img);
            this.gLQ = Graphics.FromImage(img);
            gLQ.PixelOffsetMode = PixelOffsetMode.HighSpeed;
            gLQ.SmoothingMode = SmoothingMode.HighSpeed;
            gLQ.InterpolationMode = InterpolationMode.Low;
            gLQ.CompositingQuality = CompositingQuality.HighSpeed;
            gLQ.CompositingMode = CompositingMode.SourceCopy;
            gState[(int)RenderingMode.LowQuality] = gLQ.Save();

            gLQ.SmoothingMode = SmoothingMode.AntiAlias;
            gLQ.CompositingQuality = CompositingQuality.HighQuality;
            gLQ.CompositingMode = CompositingMode.SourceOver;
            gLQ.InterpolationMode = InterpolationMode.High;
            gLQ.PixelOffsetMode = PixelOffsetMode.HighQuality;
            gState[(int)RenderingMode.HighQuality] = gLQ.Save();
            //this.g2 = Graphics.FromImage(img);
        }
    }
}